﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;

using FlatRedBall;

using ROMAP.Entities.Enemies;
using ROMAP.Entities;
using ROMAP.Entities.Tiles;

using Microsoft.Xna.Framework;

using ROMAP.Screens;

namespace ROMAP.Utilities
{
    public class Level
    {
        #region Fields
        private const int MAX_NUM_LEVELS = 8;
        private const float WINTILE_ANIM_DISTANCE = 20.0f;

        private int _levelNumber;
        private int _deathCounter = 0;

        private List<Enemy> _enemies;
        private Player _player;
        private TileGrid _tileGrid;
        private StartTile _startTile;
        private List<WinTile> _winTile;
        private List<PulsedTile> _pulsedTiles;
        private List<PulsedTileControl> _pulseInstances = new List<PulsedTileControl>();

        private bool initialized = false;
        private bool death = false;

        public bool End
        {
            get;
            set;
        }
        public bool Next
        {
            get;
            set;
        }

        public int DeathCounter
        {
            get { return _deathCounter; }
            set { _deathCounter = value; }
        }

        #endregion

        #region Constructors
        public Level()
        {
            Initialize(1);
        }

        public Level(int levelNumber)
        {
            Initialize(levelNumber);
        }

        #endregion

        #region Initialization

        private void Initialize(int levelNumber)
        {
            _levelNumber = levelNumber;
            LoadLevel();
            initialized = true;
        }

        private void LoadLevel()
        {
            XmlDocument doc = new XmlDocument();

            //doc.Load(@"Content/Level/test.xml");
            doc.Load(@"Content/Level/level" + GameManager.LevelNum + ".xml");

            XmlNodeList length = doc.GetElementsByTagName("Length");
            XmlNodeList height = doc.GetElementsByTagName("Height");
            XmlNodeList grounds = doc.GetElementsByTagName("Ground");
            XmlNodeList start = doc.GetElementsByTagName("Start");
            XmlNodeList wins = doc.GetElementsByTagName("Win");
            XmlNodeList deads = doc.GetElementsByTagName("Dead");

            XmlNodeList sentries = doc.GetElementsByTagName("Sentry");
            XmlNodeList flying = doc.GetElementsByTagName("FlyingEnemy");

            _tileGrid = new TileGrid();
            _startTile = new StartTile();
            _enemies = new List<Enemy>();
            _winTile = new List<WinTile>();
            _pulsedTiles = new List<PulsedTile>();

            foreach (XmlNode g in grounds)
            {
                int x = Int32.Parse(g.ChildNodes[0].InnerText);
                int y = Int32.Parse(g.ChildNodes[1].InnerText);
                string type = (g.ChildNodes[2].InnerText);
                string number = (g.ChildNodes[3].InnerText);

                GroundTile gt = new GroundTile(x, y, "1");
                //PulsedTile gt = new PulsedTile(x, y);

                _tileGrid.Add(gt);
            }

            foreach (XmlNode s in start)
            {
                int x = Int32.Parse(s.ChildNodes[0].InnerText);
                int y = Int32.Parse(s.ChildNodes[1].InnerText);

                _startTile.TilePosition = new Vector2i(x, y);
                break;
            }

            foreach (XmlNode w in wins)
            {
                int x = Int32.Parse(w.ChildNodes[0].InnerText);
                int y = Int32.Parse(w.ChildNodes[1].InnerText);

                WinTile wt = new WinTile(x, y);

                _tileGrid.Add(wt);
                _winTile.Add(wt);
            }

            foreach (XmlNode d in deads)
            {
                int x = Int32.Parse(d.ChildNodes[0].InnerText);
                int y = Int32.Parse(d.ChildNodes[1].InnerText);

                Electricity dt = new Electricity(x, y, float.Parse(d.ChildNodes[2].InnerText));

                _tileGrid.Add(dt);
            }

            foreach (XmlNode b in sentries)
            {
                int x = Int32.Parse(b.ChildNodes[0].InnerText);
                int y = Int32.Parse(b.ChildNodes[1].InnerText);

                SentryEnemy be = new SentryEnemy(x, y, Boolean.Parse(b.ChildNodes[2].InnerText));
                _enemies.Add(be);
            }

            foreach (XmlNode b in flying)
            {
                int x = Int32.Parse(b.ChildNodes[0].InnerText);
                int y = Int32.Parse(b.ChildNodes[1].InnerText);

                FlyingEnemy be = new FlyingEnemy(x, y, float.Parse(b.ChildNodes[2].InnerText), Boolean.Parse(b.ChildNodes[3].InnerText));
                _enemies.Add(be);
            }

            _player = new Player("Global");
            _player.Position = _startTile.RelativePosition;

            if (GameManager.LevelNum == 1)
            {
                _winTile[0].RelativeX -= 1.5f;
                _winTile[0].RelativeY -= 1.5f;
            }
            else if (GameManager.LevelNum == 2)
            {
                _winTile[0].RelativeX += 0f;
                _winTile[0].RelativeY -= 1.5f;
            }
            else if (GameManager.LevelNum == 3)
            {
                _winTile[0].RelativeX += 1.5f;
                _winTile[0].RelativeY -= 1.5f;
            }
            else if (GameManager.LevelNum == 4)
            {
                _winTile[0].RelativeX += 1.5f;
                _winTile[0].RelativeY -= 1.5f;
            }
            else if (GameManager.LevelNum == 5)
            {
                _winTile[0].RelativeX += 1.5f;
                _winTile[0].RelativeY -= 1.5f;
            }
            else if (GameManager.LevelNum == 6)
            {
                _winTile[0].RelativeX += 3.0f;
                _winTile[0].RelativeY -= 1.5f;
            }
            else if (GameManager.LevelNum == 7)
            {
                _winTile[0].RelativeY -= 1.5f;
            }
            else if (GameManager.LevelNum == 8)
            {
                _winTile[0].RelativeY -= 1.5f;

                _enemies[5].xstart += 2.5f;
                _enemies[6].xstart -= 2.5f;
                _enemies[5].X += 2.5f;
                _enemies[6].X -= 2.5f;
            }
        }

        public void RestartLevel()
        {
            _deathCounter++;
            Destroy();
            LoadLevel();
            death = false;
        }

        public void NextLevel()
        {
            GameManager.LevelNum++;
            if (GameManager.LevelNum <= MAX_NUM_LEVELS)
                Next = true;
            else
                Score = true;
        }

        public void Destroy()
        {
            _tileGrid.Destroy();
            _player.Destroy();
            _startTile.Destroy();

            foreach (Enemy e in _enemies)
            {
                e.Destroy();
            }
            foreach (PulsedTile pt in _pulsedTiles)
                pt.Destroy();
            //foreach (PulsedTileControl c in _pulsedTiles)
            //    c.Destroy();
        }

        #endregion

        #region Public Methods
        public void Update()
        {
            if (initialized)
            {
                if (KeyboardManager.Restart())
                    End = true;

                if (!death)
                    _player.Activity();
                else
                    _player.CheckDeath();

                CheckPulseAreaCollision();
                CheckPulsedTiles();

                if (_player.Pulsing)
                    Pulse();

                CheckPortal();

                foreach (Enemy e in _enemies)
                {
                    e.Activity();
                }

                DetectCollisions();

                if (_player.CueRestart)
                    RestartLevel();

            }
        }

        #region Pulse Version 1
        /*
        public void Pulse()
        {
            Vector3 finalVector = Vector3.Zero;

            finalVector = PulseTileCollision();
            //finalVector = PulseObjectCollision();

            _player.Pulse(finalVector);
        }
        
        private Vector3 PulseTileCollision()
        {
            Vector3 finalVector = Vector3.Zero;

            List<Tile> pulseTiles = _tileGrid.GetPulseTiles(_player);

            foreach (Tile tile in pulseTiles)
            {
                if (_player.PulseCollision.CollideAgainst(tile.Collision))
                    finalVector += _player.Position - tile.Position;
            }

            return finalVector;
        } 
         */
        #endregion

        #region Pulse Version 2
        /*
        private void Pulse()
        {
            float yVelTotal = 0.0f;
            float xVelTotal = 0.0f;

            if (_player.FinalPulseCollision.Radius <= _player.PulseCollision.Radius
                && _player.FinalPulseCollision.Radius > 0.0f)
            {
                List<Tile> pulseTiles = _tileGrid.GetPulseTiles(_player);

                 foreach (Tile tile in pulseTiles)
                {
                    if (tile is GroundTile && _player.FinalPulseCollision.CollideAgainstMove(tile.Collision, 0, 1))
                    {
                        //Detects collision when on top of a ground tile
                        if (_player.FinalPulseCollision.LastMoveCollisionReposition.Y > 0.0f)
                        {
                            yVelTotal += 1.0f * _player.PulseCollision.Radius;
                        }
                        //Detects collision on the bottom of a ground tile
                        else if (_player.FinalPulseCollision.LastMoveCollisionReposition.Y < 0.0f)
                        {
                            yVelTotal -= 1.0f * _player.PulseCollision.Radius;
                        }
                        //Detects collision on the left of ground tile
                        else if (_player.FinalPulseCollision.LastMoveCollisionReposition.X > 0.0f)
                        {
                            xVelTotal -= 1.0f * _player.PulseCollision.Radius;
                        }
                        //Detects collision on the right of ground tile
                        else if  (_player.FinalPulseCollision.LastMoveCollisionReposition.X < 0.0f)
                        {
                            xVelTotal += 1.0f * _player.PulseCollision.Radius;
                        }
                    }
                }
                 
                 _player.YVelocity += yVelTotal;
                 _player.XVelocity += xVelTotal;

                 //_player.ChangeDirection(_player.XVelocity);
            }
            else if (_player.FinalPulseCollision.Radius > _player.PulseCollision.Radius)
            {
                _player.FinalPulseCollision.RadiusVelocity = 0.0f;
                _player.FinalPulseCollision.Radius = 0.0f;
                _player.PulseCollision.Radius = 0.0f;
                _player.Pulsing = false;
                _player.CanPulse = true;
            }
        }
         */
        #endregion

        #region Pulse Version 3
        private void Pulse()
        {
            float yVelTotal = 0.0f;
            float xVelTotal = 0.0f;
            float tileDistance = float.MaxValue;

            bool pulseUp = false;       // If true, getting pulsed up;
            bool pulseDown = false;     // If true, getting pulsed down;
            bool pulseSide = false;     // If true, getting pulsed in the x direction
            bool twoOrEight = false;    // If true, then there is a tile in the 2 or 8 position

            float yPulseScale = 9.0f;
            float xPulseScale = 6f;

            int pulsedTileX = -1;
            int pulsedTileY = -1;

            List<Tile> pulseTiles = _tileGrid.GetPulseTiles(_player);

            #region Pulsing on Tiles
            foreach (Tile tile in pulseTiles)
            {

                // Giving different velocities based on the position of the surrounding tiles.
                //
                // 7 8 9  - 8 = tile directly above player; 
                // 4 5 6  - 5 = player tile position; 4 = tile directly to the left; 6 = tile directly to the right;
                // 1 2 3  - 2 = tile directly below player; 
                // 7, 8, 9, 1, 2, and 3 affect the Y velocity
                // 4 and 6 affect X Velocity
                if (tile is GroundTile && _player.PulseCollision.CollideAgainst(tile.Collision))
                {
                    if (tile.TileX == _player.TileX)
                    {
                        // Checking position 2
                        if (tile.Position.Y < _player.Position.Y)
                        {
                            pulseUp = true;
                            twoOrEight = true;

                            // Gets the closest tile to the player that will be lighting up when pulsed on
                            if (CheckDistance(tile) < tileDistance)
                            {
                                pulsedTileX = tile.TileX;
                                pulsedTileY = tile.TileY;

                                tileDistance = CheckDistance(tile);
                            }
                        }

                        // Checking position 8
                        else
                        {
                            pulseDown = true;
                            twoOrEight = true;

                            if (CheckDistance(tile) < tileDistance)
                            {
                                pulsedTileX = tile.TileX;
                                pulsedTileY = tile.TileY;

                                tileDistance = CheckDistance(tile);
                            }
                        }
                    }
                    else if (tile.TileX < _player.TileX)
                    {
                        // Checking position 4
                        if (tile.TileY == -_player.TileY)
                        {
                            xVelTotal += xPulseScale * _player.PulseCollision.Radius;

                            pulseSide = true;

                            if (!twoOrEight && CheckDistance(tile) < tileDistance)
                            {
                                pulsedTileX = tile.TileX;
                                pulsedTileY = tile.TileY;

                                tileDistance = CheckDistance(tile);
                            }
                        }

                        // Checking position 7
                        else if (!pulseSide && tile.Position.Y > _player.Position.Y)
                            pulseDown = true;

                        // Checking position 1
                        else if (!pulseSide && tile.Position.Y < _player.Position.Y)
                        {
                            pulseUp = true;

                            if (!twoOrEight && CheckDistance(tile) < tileDistance)
                            {
                                pulsedTileX = tile.TileX;
                                pulsedTileY = tile.TileY;

                                tileDistance = CheckDistance(tile);
                            }
                        }
                    }
                    else
                    {
                        // Checking position 6
                        if (tile.TileY == -_player.TileY)
                        {
                            xVelTotal -= xPulseScale * _player.PulseCollision.Radius;

                            pulseSide = true;

                            if (!twoOrEight && CheckDistance(tile) < tileDistance)
                            {
                                pulsedTileX = tile.TileX;
                                pulsedTileY = tile.TileY;

                                tileDistance = CheckDistance(tile);
                            }
                        }

                        // Checking position 3
                        else if (!pulseSide && tile.Position.Y < _player.Position.Y)
                        {
                            pulseUp = true;

                            if (!twoOrEight && CheckDistance(tile) < tileDistance)
                            {
                                pulsedTileX = tile.TileX;
                                pulsedTileY = tile.TileY;

                                tileDistance = CheckDistance(tile);
                            }
                        }
                        // Checking position 9
                        else if (!pulseSide && tile.Position.Y > _player.Position.Y)
                        {
                            pulseDown = true;

                            if (!twoOrEight && CheckDistance(tile) < tileDistance)
                            {
                                pulsedTileX = tile.TileX;
                                pulsedTileY = tile.TileY;

                                tileDistance = CheckDistance(tile);
                            }
                        }
                    }
                }
            }

            if (pulseSide && !twoOrEight)
            {
                pulseUp = false;
                pulseDown = false;
            }
            #endregion

            #region Pulsing on Enemies
            foreach (Enemy e in _enemies)
            {
                 if (e.TileY <= _player.TileY && _player.PulseCollision.CollideAgainst(e.Collision))
                    pulseUp = true;
            }

            #endregion

            if (pulseUp)
            {
                yVelTotal = yPulseScale * _player.PulseCollision.Radius;

                _player.YVelocity = yVelTotal;
            }
            else if (pulseDown)
                yVelTotal = -yPulseScale * _player.PulseCollision.Radius * 2;

            _player.XVelocity += xVelTotal; 

            if (_player.YVelocity != 0.0f)
                _player.JumpState = JumpState.Jump;

            if (xVelTotal != 0)
                _player.ChangeDirection(_player.XVelocity);

            if (pulsedTileX >= 0 && pulsedTileY >= 0)
            {
                PulsedTileControl pulsedTiles = new PulsedTileControl(pulsedTileX, pulsedTileY);
                _pulseInstances.Add(pulsedTiles);
            }

            _player.FinalPulseCollision.RadiusVelocity = 0.0f;
            _player.FinalPulseCollision.Radius = 0.0f;
            _player.PulseCollision.Radius = 0.0f;
            _player.Pulsing = false;
            _player.CanPulse = true;
        }
        #endregion

        #endregion

        #region Private Methods
        private void DetectCollisions()
        {
            bool grdCollide = HandleGroundCollision();
            //bool objCollide = HandleObjectCollision();
            EnemyCollision();


            if (!grdCollide) // && !objCollide)
            {
                _player.JumpState = JumpState.InAir;
            }
        }

        private bool HandleObjectCollision()
        {
            /*
            bool hasCollided = false;

            foreach (Obstacle o in _obstacles)
            {
                //do we collide?
                if (_player.Collision.CollideAgainst(o.Collision))
                {
                    //is it dangerous?
                    if (o.Dangerous)
                    {
                        hasCollided = true;
                        ResetLevel(false);
                    }
                    else
                    {
                        //where is it?
                        //Detects collision when on top of a ground tile
                        if (_player.Collision.LastMoveCollisionReposition.Y > 0.0f && _player.JumpState != JumpState.Jump)
                        {
                            hasCollided = true;
                            _player.YVelocity = 0.0f;
                            _player.JumpState = JumpState.None;
                        }
                        //Detects collision on the bottom of a ground tile
                        else if (_player.Collision.LastMoveCollisionReposition.Y < 0.0f)
                        {
                            hasCollided = true;
                            _player.YVelocity = 0.0f;
                        }
                        //Detects collision on the right or left of ground tile
                        if (_player.Collision.LastMoveCollisionReposition.X < 0.0f
                            || _player.Collision.LastMoveCollisionReposition.X > 0.0f
                            && _player.JumpState != JumpState.Jump)
                        {
                            hasCollided = true;
                            _player.XVelocity = 0.0f;
                        }
                    }
                }
            }

            return hasCollided;
             */

            return false;
        }

        // Handling of ground collisions with the player
        private bool HandleGroundCollision()
        {
            // Ground Collision
            bool hasCollided = false;

            List<Tile> adjacentTiles = _tileGrid.GetAdjacentTiles(_player);

            // loop through all the tiles that will be tested for collision
            foreach (Tile tile in adjacentTiles)
            {
                #region WinTile Collision
                if (tile is WinTile && _player.Collision.CollideAgainst(tile.Collision))
                {
                    NextLevel();
                }
                #endregion

                #region GroundTile Collision

                else if (tile is GroundTile && _player.Collision.CollideAgainstMove(tile.Collision, 0, 1))
                {
                    //Detects collision when on top of a ground tile
                    if (_player.YVelocity < 0.0f && _player.Collision.LastMoveCollisionReposition.Y > 0.0f && _player.JumpState != JumpState.Jump)
                    {
                        hasCollided = true;
                        _player.YVelocity = 0.0f;
                        _player.YAcceleration = 0.0f;
                        _player.JumpState = JumpState.None;
                    }
                    //Detects collision on the bottom of a ground tile
                    else if (_player.YVelocity > 0 && _player.Collision.LastMoveCollisionReposition.Y < 0.0f)
                        _player.YVelocity = 0.0f;

                    //Detects collision on the right or left of ground tile
                    if (_player.XVelocity > 0.0f && _player.Collision.LastMoveCollisionReposition.X < 0.0f)
                        _player.XVelocity = 0.0f;

                    else if (_player.XVelocity < 0.0f && _player.Collision.LastMoveCollisionReposition.X > 0.0f)
                        //&& _player.JumpState != JumpState.Jump)
                        _player.XVelocity = 0.0f;
                }
                else if (!death && tile is Electricity && _player.Collision.CollideAgainst(tile.Collision))
                {
                    Death();
                    break;
                }

                #endregion
            }

            return hasCollided;
        }

        private void EnemyCollision()
        {
            foreach (Enemy e in _enemies)
            {
                if (!death && _player.Collision.CollideAgainstMove(e.Collision, 0, 1))
                {
                    Death();
                    break;
                }
            }
        }

        private void Death()
        {
            if (!_player.Testing)
            {
                _player.Velocity = Vector3.Zero;
                _player.Acceleration = Vector3.Zero;
                _player.JumpState = JumpState.None;
                _player.PulseCollision.Radius = 0.0f;
                _player.PulseCollision.RadiusVelocity = 0.0f;
                death = true;
                _player.DeathAnimation();
            }
        }

        private void CheckPortal()
        {
            foreach (WinTile w in _winTile)
            {
                if (CheckDistance(w) <= WINTILE_ANIM_DISTANCE)
                {
                    w.PlayerIsClose(true);
                }
                else
                    w.PlayerIsClose(false);

                w.Activity();
            }
        }

        private float CheckDistance(Tile t)
        {
            return (float)Math.Sqrt(Math.Pow((double)(t.X - _player.X), 2.0d) + Math.Pow((double)(t.Y - _player.Y), 2.0d));
        }

        private void CheckPulseAreaCollision()
        {
            bool inArea = false;

            List<Tile> pulseTiles = _tileGrid.GetPulseTiles(_player);

            foreach (Tile tile in pulseTiles)
            {
                if (tile is GroundTile && _player.PulseCollision.CollideAgainst(tile.Collision))
                    inArea = true;
            }

            foreach (Enemy e in _enemies)
            {
                if ( _player.PulseCollision.CollideAgainst(e.Collision))
                {
                    inArea = true;
                }
            }

            if (inArea)
            {
                _player.PulseAreaRed = 0.0f;
                _player.PulseAreaBlue = 0.0f;
            }
            else
            {
                _player.PulseAreaRed = 1.0f;
                _player.PulseAreaBlue = 1.0f;
            }
        }

        private void CheckPulsedTiles()
        {
            foreach (PulsedTile pt in _pulsedTiles)
            {
                pt.Activity();
            }
            foreach (PulsedTileControl p in _pulseInstances)
            {
                if (p.Tier <= p.MaxTiers)
                {
                    if (TimeManager.CurrentTime - p.LastTierChange > p.EffectDelay)
                    {
                        p.Tier++;
                        p.LastTierChange = TimeManager.CurrentTime;
                    }
                }
                
                foreach (Vector3 t in p.AffectedTiles)
                {
                    Vector2i tPos = new Vector2i();
                    tPos.X = (int)t.X;
                    tPos.Y = (int)t.Y;

                    if (t.Z == p.Tier && _tileGrid.Contains(tPos, typeof(GroundTile)))
                    {
                        PulsedTile pulsedTile = new PulsedTile((int)t.X, (int)t.Y);
                        _tileGrid.Add(pulsedTile);
                        _pulsedTiles.Add(pulsedTile);
                    }
                }
            }
        }
        #endregion

        public bool Score { get; set; }
    }
}

